#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import re

from common.cleaner import clear
from common.const import DeployType
from generaldb.gbase8a import LOGGER
from generaldb.gbase8a.commands.gbase_cmd import GbaseCmd
from generaldb.gbase8a.common.const import RoachConstant, Status, ErrorCode, NormalErr, VCMode, SUPPORT_VERSION, \
    PexpectResult
from generaldb.gbase8a.common.gbase_common import (
    gbase_check_user_name_and_injection,
    get_value_by_ip_and_keyword,
    get_value_by_keyword_row)
from generaldb.gbase8a.common.gbase_sql_tool import SqlCmdTool
from generaldb.gbase8a.util.gbase_util import GbaseUtil


class GBaseInfos:
    sys_user = ''

    def __init__(self, pid, cmd: GbaseCmd = None):
        self.pid = pid
        self.cmd = cmd
        self.db_user_name = self.cmd.get_db_user()
        # 自定义参数
        self.sys_user = self.cmd.get_os_user()

    @staticmethod
    def parse_data_cluster(info_lines):
        ip_index = -1
        catalogue_line = ""
        for line in info_lines.split("\n"):
            if "IpAddress" in line:
                catalogue_line = line
        for idx, val in enumerate(catalogue_line.split("|")):
            if "IpAddress" in val:
                ip_index = idx
                break
        if ip_index < 0:
            LOGGER.error("Failed get ip address")
            return ''
        ip_lists = []
        for line in info_lines.split("\n"):
            if "." in line:
                ip_lists.append(line.split("|")[ip_index].strip())
        all_local_ip = GbaseUtil.extract_ip()
        for ip_address in ip_lists:
            if ip_address in all_local_ip:
                return ip_address
        return ''

    @staticmethod
    def get_local_coordinator_name(db_os_user, cmd_obj):
        gbase_cmd = cmd_obj
        ret, info_lines = gbase_cmd.show_cluster_coordinary(db_os_user)
        if not ret:
            LOGGER.error("Get coordinator cluster error!")
            return ''
        ret, data_info = gbase_cmd.show_cluster_data(db_os_user)
        if not ret:
            LOGGER.error("Get data cluster error!")
            return ''
        local_ip = GBaseInfos.parse_data_cluster(data_info)
        return get_value_by_ip_and_keyword(info_lines, local_ip, "NodeName")

    @staticmethod
    def get_local_gcware_name(db_os_user, cmd_obj):
        gbase_cmd = cmd_obj
        ret, info_lines = gbase_cmd.show_cluster_gcware(db_os_user)
        if not ret:
            LOGGER.error("Get gcware info error!")
            return ''
        ret, data_info = gbase_cmd.show_cluster_data(db_os_user)
        if not ret:
            LOGGER.error("Get data cluster error!")
            return ''
        local_ip = GBaseInfos.parse_data_cluster(data_info)
        return get_value_by_ip_and_keyword(info_lines, local_ip, "NodeName")

    @staticmethod
    def check_deploy_type(deploy_type):
        if deploy_type != DeployType.SHARDING_TYPE:
            LOGGER.error("Wrong database type!")
            return ErrorCode.ERR_DEPLOY_TYPE, []
        return NormalErr.NO_ERR, []

    def get_vc_mode(self):
        info_lines = self.get_gcware_info()
        for val in info_lines.split("\n"):
            if "VIRTUAL CLUSTER MODE" in val:
                if val.split(":")[1].strip() == "NORMAL":
                    return Status.ONLINE
        return Status.OFFLINE

    def get_cluster_state(self):
        info_lines = self.get_gcware_info()
        for _, val in enumerate(info_lines.split("\n")):
            if "STATE" in val:
                if val.split(":")[1].strip() == "ACTIVE":
                    return Status.ONLINE
        return Status.OFFLINE

    def get_ip_list(self):
        """
        从gcadmin获取到所有集群中的主机的ip
        :pid pid
        """
        coord_lines = self.get_coordinator_cluster()
        gcware_lines = self.get_gcware_info()
        date_lines = self.get_data_cluster()
        result = re.findall(r"(?:[0-9]{1,3}.){3}[0-9]{1,3}", coord_lines)
        gcware_ips = re.findall(r"(?:[0-9]{1,3}.){3}[0-9]{1,3}", gcware_lines)
        for gcware_ip in gcware_ips:
            if gcware_ip not in result:
                result.append(gcware_ip)
        data_ips = re.findall(r"(?:[0-9]{1,3}.){3}[0-9]{1,3}", date_lines)
        for data_ip in data_ips:
            if data_ip not in result:
                result.append(data_ip)
        if self.get_vc_type() == VCMode.MULTI:
            vc_ips = self.get_vc_ips()
            for vc_ip in vc_ips:
                if vc_ip not in result:
                    result.append(vc_ip)
        return result

    def get_local_ip_address(self):
        info_lines = self.get_data_cluster()
        return GBaseInfos.parse_data_cluster(info_lines)

    def get_local_distribute_id(self):
        info_lines = self.get_data_cluster()
        local_ip = self.get_local_ip_address()
        return get_value_by_ip_and_keyword(info_lines, local_ip, "DistributionId")

    def get_all_nodes_info(self):
        """
        自定义字典，根据备份恢复需要返回所有需要信息
        :pid pid
        """
        LOGGER.info("Get all node info!")
        all_node = {"vc_type": "single", "vc_names": [], "cluster_status": "", "vc_mode": "", "nodes": []}
        coord_lines = self.get_coordinator_cluster()
        data_lines = self.get_data_cluster()
        gcware_lines = self.get_gcware_info()
        all_node["cluster_status"] = self.get_cluster_state()
        all_node["vc_mode"] = self.get_vc_mode()
        ip_list = self.get_ip_list()
        for ip_address in ip_list:
            node = {
                "ip_address": ip_address,
                "gcware_status": get_value_by_ip_and_keyword(gcware_lines, ip_address, "gcware"),
                "coordinator_name": get_value_by_ip_and_keyword(coord_lines, ip_address, "NodeName"),
                "distribution_id": get_value_by_ip_and_keyword(data_lines, ip_address, "DistributionId"),
                "gcluster_status": get_value_by_ip_and_keyword(coord_lines, ip_address, "gcluster"),
                "data_node_name": get_value_by_ip_and_keyword(data_lines, ip_address, "NodeName")
            }
            all_node["nodes"].append(node)
        LOGGER.info("Succeed all node info!")
        return all_node

    def check_nodes_topo(self, nodes_list):
        """
        从判断所选机器是否属于集群
        :pid pid
        """
        ip_list = self.get_ip_list()
        if len(nodes_list) != len(ip_list):
            LOGGER.error("Different host numbers!")
            return ErrorCode.ERR_CHOSEN_HOSTS, []
        for ip_address in ip_list:
            is_find = False
            for ips in nodes_list:
                if ip_address in ips:
                    is_find = True
            if not is_find:
                LOGGER.error("Different topo!")
                return ErrorCode.ERR_CHOSEN_HOSTS, []
        return NormalErr.NO_ERR, []

    def check_environment(self):
        """
        判断集群是否能够进行备份恢复
        """
        coord_lines = self.get_coordinator_cluster()
        gcware_lines = self.get_gcware_info()
        coord_ips = re.findall(r"(?:[0-9]{1,3}.){3}[0-9]{1,3}", coord_lines)
        gcware_ips = re.findall(r"(?:[0-9]{1,3}.){3}[0-9]{1,3}", gcware_lines)
        is_find = False
        for coord_ip in coord_ips:
            if coord_ip in gcware_ips:
                is_find = True
                break
        if not is_find:
            return ErrorCode.ERR_ENVIRONMENT, []
        return NormalErr.NO_ERR, []

    def check_auth(self):
        if gbase_check_user_name_and_injection(self.sys_user) \
                or not self.cmd.get_stdin_field(GbaseCmd.get_os_pwd_key()):
            return ErrorCode.ERROR_AUTH, []
        ret, err = self.cmd.check_os_user()
        if not ret:
            LOGGER.error(f"auth failed, error:%s", err)
            return ErrorCode.ERROR_AUTH, []
        return NormalErr.NO_ERR, []

    def check_sys_user(self):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.GCADMIN_ALL)
        if not return_code:
            LOGGER.error("Get os user error!")
            return ErrorCode.LOGIN_FAILED, []
        return NormalErr.NO_ERR, []

    def is_skip_verify(self):
        """
        是否跳过资源注册校验（无gcadmin节点）
        """
        ret, out = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.GCADMIN_ALL)
        if not ret and "command not found" in out:
            return True
        return False

    def check_cluster_service(self):
        LOGGER.info("Get all service info")
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.ALL_SERVICE_INFO)
        if not return_code:
            LOGGER.error("Get all service error!")
            return ErrorCode.ERR_DB_SERVICES, []
        for line in out_info.split("\n"):
            if line and "running" not in line:
                return ErrorCode.ERR_DB_SERVICES, []
        return NormalErr.NO_ERR, []

    def check_database_engine(self, present_name, vc_name):
        """
        判断数据库引擎是否为express
        present_name 数据库名
        """
        expect_envs = [(self.cmd.get_db_pwd_key(), PexpectResult.DB_LOGIN)]
        os_user = self.cmd.get_os_user()
        db_user = self.cmd.get_db_user()
        ret, message = self.cmd.get_db_engine_exp(os_user, db_user, present_name, expect_envs, vc_name)
        if not ret:
            LOGGER.error("Get database engine failed!")
            return ErrorCode.ERR_DB_SERVICES, []
        LOGGER.info("Get database engine successful.")
        if message.strip():
            LOGGER.error("Database with express engine is not allowd.")
            return ErrorCode.ERROR_DB_EXPRESS, []
        return NormalErr.NO_ERR, []

    def check_database_exist(self, present_name, vc_name, param_inst):
        """
        判断数据库是否存在
        present_name 数据库名
        vc_name vc名，单vc传入空
        """
        db_user_password = self.cmd.get_db_pwd()
        if gbase_check_user_name_and_injection(db_user_password):
            LOGGER.error("Get user name err")
            return ErrorCode.ERROR_USER_NOT_EXIST_CLUSTER, []
        # 交互式查询show databases 是否存在库名
        sql_cmd_inst = SqlCmdTool(self.sys_user, self.db_user_name, db_user_password)
        if not vc_name:
            result, databases = sql_cmd_inst.run_sql_tool("show databases;")
        else:
            result, databases = sql_cmd_inst.run_sql_tool(f"use vc {vc_name};", "show databases;")
        sql_cmd_inst.close_child()
        if not result:
            LOGGER.error("Execute cmd failed!")
            if databases == "VC ERROR":
                return ErrorCode.ERR_VC_NAME_WRONG, []
            elif databases == "VC NEEDED":
                return ErrorCode.ERR_VC_NAME_WRONG, []
            return ErrorCode.LOGIN_FAILED, []
        if present_name and present_name.lower() not in databases:
            LOGGER.error("Database name err!")
            return ErrorCode.ERR_DB_NOT_EXIST_ON_NODE, [GbaseUtil.get_current_node_ip(param_inst), present_name]
        clear(db_user_password)
        return NormalErr.NO_ERR, []

    def check_version(self):
        code, version = self.get_version()
        if code != NormalErr.NO_ERR:
            return NormalErr.FALSE, []
        if SUPPORT_VERSION in version:
            return NormalErr.NO_ERR, []
        return ErrorCode.ERR_NOT_SUPPORT_VERSION, []

    def get_version(self):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.VERSION)
        if not return_code:
            LOGGER.error("Get all service error!")
            return NormalErr.FALSE, []
        # gbase ver 9.5.3.14.121230, for unknown-linux-gnu (x86_64) using readline 6.3
        version = out_info.split(",")[0].replace("gbase ver", "").strip()
        if not version:
            LOGGER.error("Failed to get version")
            return NormalErr.FALSE, []
        return NormalErr.NO_ERR, version

    def get_coordinator_cluster(self):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.COORDINATOR_CLUSTER)
        if not return_code:
            LOGGER.error("Get coordinator cluster error!")
            return ''
        return out_info

    def get_gcware_info(self):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.GCWARE_CLUSTER)
        if not return_code:
            LOGGER.error("Get gcware info error!")
            return ''
        return out_info

    def get_vc_info(self, vc_name):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, f"{RoachConstant.GCADMIN_VC_NAME} {vc_name}")
        if not return_code:
            LOGGER.error("Get gcware info error!")
            return ''
        return out_info

    def get_data_cluster(self):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.DATA_CLUSTER)
        if not return_code:
            LOGGER.error("Get data cluster error!")
            return ''
        return out_info

    def get_vc_type(self):
        return_code, out_info = self.cmd.su_exec_cmd(self.sys_user, RoachConstant.DATA_CLUSTER)
        if not return_code:
            LOGGER.error("Get data cluster error!")
            return ''
        if "VcName" in out_info:
            return VCMode.MULTI
        return VCMode.SINGLE

    def get_vc_ips(self):
        """
        获取所有vc下节点ip地址
        """
        info_lines = self.get_data_cluster()
        vc_names = get_value_by_keyword_row(info_lines, "VcName")
        vc_ips = []
        for vc_name in vc_names:
            vc_lines = self.get_vc_info(vc_name)
            vc_ips.extend(get_value_by_keyword_row(vc_lines, "IpAddress"))
        return vc_ips
